﻿#region

using System;
using System.Collections.Generic;
using System.Reflection;

using Toolbox.Algorithms;

#endregion

namespace Toolbox.Dynamic
{
    public interface IGroupProxyHandler<IProtocol>
    {
        void Call(MethodInfo method, object[] args);
        IDictionary<IProtocol, T> Call<T>(MethodInfo method, object[] args);
    }

    public class DynamicGroupProxy<IProtocol> : DynamicGroupProxy<IProtocol, IProtocol>
        where IProtocol : class
    {
        protected DynamicGroupProxy(IGroupProxyHandler<IProtocol> handler) : base(handler) {}
    }

    public class DynamicGroupProxy<IProtocol, IGroupProtocol> where IProtocol : class
                                                              where IGroupProtocol : class
    {
        private readonly IGroupProtocol _proxy;

        protected DynamicGroupProxy(IGroupProxyHandler<IProtocol> handler)
        {
            _proxy = DynamicProxy<IGroupProtocol>.Create(new Handler(handler));
        }

        protected IGroupProtocol Proxy
        {
            get { return _proxy; }
        }

        public static IGroupProtocol Create(IEnumerable<IProtocol> group)
        {
            Contract.NotNull(group, "group");

            return Create(new GroupHandler(group));
        }

        public static IGroupProtocol Create(IGroupProxyHandler<IProtocol> handler)
        {
            Contract.NotNull(handler, "handler");

            return (new DynamicGroupProxy<IProtocol, IGroupProtocol>(handler)).Proxy;
        }

        #region Nested type: GroupHandler

        private class GroupHandler : IGroupProxyHandler<IProtocol>
        {
            private readonly IEnumerable<IProtocol> _group;

            public GroupHandler(IEnumerable<IProtocol> group)
            {
                _group = group;
            }

            #region IGroupProxyHandler<IProtocol> Members

            public void Call(MethodInfo method, object[] args)
            {
                foreach (IProtocol p in _group)
                    method.Invoke(p, args);
            }

            public IDictionary<IProtocol, T> Call<T>(MethodInfo method, object[] args)
            {
                IDictionary<IProtocol, T> r = new Dictionary<IProtocol, T>();

                foreach (IProtocol p in _group)
                    r[p] = (T) method.Invoke(p, args);

                return r;
            }

            #endregion
        }

        #endregion

        #region Nested type: Handler

        private class Handler : IProxyHandler
        {
            private static readonly Dictionary<MethodInfo, MethodInfo> callMap =
                new Dictionary<MethodInfo, MethodInfo>();

            private static readonly Dictionary<MethodInfo, MethodInfo> methodMap =
                new Dictionary<MethodInfo, MethodInfo>();

            private readonly IGroupProxyHandler<IProtocol> _handler;

            static Handler()
            {
                Type proxyType = typeof (IGroupProxyHandler<IProtocol>);
                MethodInfo[] proxyMethods = proxyType.GetMethods();
                CheckSanity(proxyMethods.Length == 2);

                MethodInfo callVoid = proxyMethods[0];
                MethodInfo callT = proxyMethods[1];

                CheckSanity(callVoid.Name == "Call");
                CheckSanity(callT.Name == "Call");

                CheckSanity(callVoid.ReturnType == typeof (void));
                CheckSanity(callT.IsGenericMethodDefinition);

                SortedList<string, MethodInfo> front = new SortedList<string, MethodInfo>();
                foreach (MethodInfo m in typeof (IGroupProtocol).GetMethods()) {
                    string name = MethodName(m);
                    front[name] = m;
                }

                SortedList<string, MethodInfo> back = new SortedList<string, MethodInfo>();
                foreach (MethodInfo m in typeof (IProtocol).GetMethods()) {
                    string name = MethodName(m);
                    back[name] = m;
                }

                foreach (string name in back.Keys) {
                    RuntimeAssert.IsTrue(
                        front.ContainsKey(name),
                        string.Format("IGroupProtocol does not implement IProtocol->{0}.", name));

                    MethodInfo mf = front[name];
                    MethodInfo mb = back[name];

                    if (mb.ReturnType == typeof (void)) {
                        RuntimeAssert.IsTrue(
                            mf.ReturnType == typeof (void),
                            string.Format(
                                "{0} returns void in IProtocol, but returns {1} in IGroupProtocol.",
                                name, mf.ReturnType.Name));

                        callMap[mf] = callVoid;
                    } else {
                        string error =
                            string.Format(
                                "IGroupProtocol->{0} does not have the required return type of IDictionary<{1}, {2}>.",
                                name, typeof (IProtocol).Name, mb.ReturnType.Name);

                        RuntimeAssert.IsTrue(mf.ReturnType.IsGenericType, error);
                        RuntimeAssert.IsTrue(
                            mf.ReturnType.GetGenericTypeDefinition() == typeof (IDictionary<,>),
                            error);

                        Type[] genericArguments = mf.ReturnType.GetGenericArguments();

                        RuntimeAssert.IsTrue(genericArguments[0] == typeof (IProtocol), error);
                        RuntimeAssert.IsTrue(genericArguments[1] == mb.ReturnType, error);

                        callMap[mf] = callT.MakeGenericMethod(genericArguments[1]);
                    }

                    methodMap[mf] = mb;
                }
            }

            public Handler(IGroupProxyHandler<IProtocol> handler)
            {
                _handler = handler;
            }

            #region IProxyHandler Members

            public object Call(MethodInfo method, object[] args)
            {
                if (method.ReturnType == typeof (void)) {
                    _handler.Call(methodMap[method], args);
                    return null;
                }

                return callMap[method].Invoke(_handler, new object[] {methodMap[method], args});
            }

            #endregion

            private static string MethodName(MethodInfo m)
            {
                string[] args = Map.ToArray(
                    m.GetParameters(),
                    delegate(ParameterInfo p) { return p.ParameterType.Name + " " + p.Name; });
                return string.Format("{0}({1})", m.Name, string.Join(", ", args));
            }

            private static void CheckSanity(bool predicate)
            {
                RuntimeAssert.IsTrue(
                    predicate,
                    "IGroupProxyHandler<IProtocol> changed. Need to update DynamicGroupProxy.Handler.");
            }
        }

        #endregion
    }
}